﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
using log4net;

namespace Ftp
{
    public partial class FrmFtp : Form
    {
        private readonly ILog _log = LogManager.GetLogger("Ftp");
        private CancellationTokenSource _cancellationTokenSource;
        private Task _task;

        public FrmFtp()
        {
            InitializeComponent();
            log4net.Config.XmlConfigurator.Configure();
        }

        private IEnumerable<FtpTarget> GetFtpTargets()
        {
            var ftpTargets = new List<FtpTarget>();

            try
            {
                var xmlRetailer = XDocument.Load(ConfigurationManager.AppSettings["ftpTargetFile"]);
                var ftps = from query in xmlRetailer.Descendants("target")
                            where
                                query.Element("inUse").Value == "true" && Directory.Exists(query.Element("localDirectory").Value)
                            select new
                            {
                                host = query.Element("host").Value,
                                userName = query.Element("userName").Value,
                                password = query.Element("password").Value,
                                bufferLength = query.Element("bufferLength").Value,
                                localDirectory = query.Element("localDirectory").Value,
                                remoteDirectory = query.Element("remoteDirectory").Value,
                                fileLengthLimitation = query.Element("fileLengthLimitation").Value,
                                resumeIfOverLimatation = query.Element("resumeIfOverLimatation").Value,
                            };

                foreach (var ftp in ftps)
                {
                    var dirs = Directory.GetDirectories(ftp.localDirectory);

                    if (!dirs.Any())
                        continue;

                    foreach (var dir in dirs)
                    {
                        var t = new FtpTarget { };
                        t.Host = ftp.host;
                        t.UserName = ftp.userName;
                        t.Password = ftp.password;
                        t.LocalDirectory = ftp.localDirectory;
                        t.RemoteDirectory = ftp.remoteDirectory;
                        t.BufferLength = Convert.ToInt32(ftp.bufferLength);
                        t.Resume = Convert.ToBoolean(ftp.resumeIfOverLimatation);
                        t.FileLengthLimitation = Convert.ToInt64(ftp.fileLengthLimitation);
                        t.Dir = new DirectoryInfo(dir);

                        ftpTargets.Add(t);
                    }
                }
            }
            catch (Exception ex)
            {
                _log.Error(string.Format("Set up ftp target failed, check config file."), ex);
            }

            return ftpTargets;
        }

        private async Task Upload(FtpTarget ftpTarget)
        {
            if (_cancellationTokenSource.IsCancellationRequested)
                return;

            if (!ftpTarget.Dir.Exists)
                return;

            var ftp = new FtpHelper.Ftp(ftpTarget.Host, ftpTarget.UserName, ftpTarget.Password);
            var remoteDir = string.Format("{0}/{1}/{2}", ftpTarget.Host, ftpTarget.RemoteDirectory, ftpTarget.Dir.Name);

            try
            {
                var remoteDirExist = await ftp.DirectoryExists(string.Format("{0}/{1}", ftpTarget.Host, ftpTarget.RemoteDirectory), ftpTarget.Dir.Name, true);
                if (!remoteDirExist)
                    await ftp.MakeDirectory(remoteDir, true);
            }
            catch (Exception ex)
            {
                _log.Error(string.Format("Check {0} exist on {1} failed. Ftp might not available at the moment.", ftpTarget.Dir.Name, string.Format("{0}/{1}", ftpTarget.Host, ftpTarget.RemoteDirectory)), ex);
                return;
            }

            var fileInfos = ftpTarget.Dir.GetFiles();
            var uploadedOrderFileByteCount = 0;
            var uploadedOrderFileCount = 0;

            try
            {
                var uploadTasks = fileInfos.Where(fi => fi.Exists).Select(async fi =>
                {
                    try
                    {
                        var resume = fi.Length > ftpTarget.FileLengthLimitation && ftpTarget.Resume;
                        var ret = await ftp.Upload(fi, string.Format("{0}/{1}", remoteDir, fi.Name), true, resume,
                                                        ftpTarget.BufferLength, true, false,
                                                        _cancellationTokenSource.Token);
                        fi.Delete();
                        return ret;
                    }
                    catch (Exception)
                    {
                        return 0;
                    }
                });

                var counts = await Task.WhenAll(uploadTasks);
                uploadedOrderFileByteCount = counts.Sum();
                uploadedOrderFileCount = counts.Count(f => f!=0);
            }
            catch (Exception ex)
            {
                _log.Error(string.Format("Error during uploading {0} to {1}. {2} and total byte {3} of those file has been uploaded.", ftpTarget.Dir, remoteDir, uploadedOrderFileCount, uploadedOrderFileByteCount), ex);
            }

            DeleteDirectory(ftpTarget.Dir);
        }

        private void DeleteDirectory(DirectoryInfo dirInfo)
        {
            try
            {
                dirInfo.Delete(true);
            }
            catch (Exception ex)
            {
                _log.Error(string.Format("Error during clean up dir {0}.", dirInfo.FullName), ex);
            }
        }

        private async void btnStart_Click(object sender, EventArgs e)
        {
            _cancellationTokenSource = new CancellationTokenSource();
            _log.Info(" Started");
            btnCancel.Enabled = true;
            btnStart.Enabled = false;
            timer1.Start();
        }

        private async void btnCancel_Click(object sender, EventArgs e)
        {
            _cancellationTokenSource.Cancel();
            timer1.Stop();
            _log.Info(" Stoped");
            btnStart.Enabled = true;
            btnCancel.Enabled = false;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (_task != null && !_task.IsCompleted)
                return;

            var ftpTargets = GetFtpTargets().ToList();

            if (ftpTargets.Count == 0)
                return;

            _task = Task.Factory.StartNew(() =>
            {
                var tasks = ftpTargets.Select(Upload).ToArray();
                Task.WaitAll(tasks);
            });
        }
    }

    internal class FtpTarget
    {
        public string LocalDirectory { set; get; }

        public string Host { set; get; }

        public string UserName { set; get; }

        public string Password { set; get; }

        public int BufferLength { set; get; }

        public string RemoteDirectory { set; get; }

        public bool Resume { set; get; }

        public Int64 FileLengthLimitation { set; get; }

        public DirectoryInfo Dir { set; get; }
    }
}
